]> dgit.raspbian.org Git - gnome-shell.git/commitdiff
PopupMenu: add horizontal sliders
authorGiovanni Campagna <scampa.giovanni@gmail.com>
Thu, 22 Jul 2010 12:34:02 +0000 (14:34 +0200)
committerDan Winship <danw@gnome.org>
Fri, 1 Oct 2010 19:18:36 +0000 (15:18 -0400)
Introduce Cairo-drawn sliders to be used in PopupMenus (for example for
volume). They are stylable to some extent (colors, border width, slider
height) and have the standard behaviour of a slider, except they are
completely modal (once you start dragging, all events are intercepted by
the slider, which thus is kept active and highlighted at all times).
They show numeric values between 0 and 1 (scaling must be performed outside)
and emit value-changed on button release, but no activate, keeping the
menu open.

https://bugzilla.gnome.org/show_bug.cgi?id=625029

data/theme/gnome-shell.css
js/ui/popupMenu.js

index 0e96be8110c6a6a0fc44079308746115174abb01..3c63794bf85be6800a1f877df89f97fb3b6b7ea8 100644 (file)
@@ -148,6 +148,15 @@ StTooltip {
     height: 1em;
 }
 
+.popup-slider-menu-item {
+    height: 1em;
+    -slider-height: 0.3em;
+    -slider-background-color: #333333;
+    -slider-border-color: #5f5f5f;
+    -slider-border-width: 1px;
+    -slider-handle-radius: 0.5em;
+}
+
 /* Switches (to be used in menus) */
 .toggle-switch {
     width: 4.5em;
index de8de1ecaebb4aacf590b4272de1eed7150399c1..7e60fe8216d40b72c0f53c1d9a00145c26f433f4 100644 (file)
@@ -4,8 +4,9 @@ const Cairo = imports.cairo;
 const Clutter = imports.gi.Clutter;
 const Gtk = imports.gi.Gtk;
 const Lang = imports.lang;
-const St = imports.gi.St;
+const Shell = imports.gi.Shell;
 const Signals = imports.signals;
+const St = imports.gi.St;
 
 const BoxPointer = imports.ui.boxpointer;
 const Main = imports.ui.main;
@@ -164,6 +165,163 @@ PopupSeparatorMenuItem.prototype = {
     }
 };
 
+function PopupSliderMenuItem() {
+    this._init.apply(this, arguments);
+}
+
+PopupSliderMenuItem.prototype = {
+    __proto__: PopupBaseMenuItem.prototype,
+
+    _init: function(value) {
+        PopupBaseMenuItem.prototype._init.call(this, { activate: false });
+
+        if (isNaN(value))
+            // Avoid spreading NaNs around
+            throw TypeError('The slider value must be a number');
+        this._displayValue = this._value = Math.max(Math.min(value, 1), 0);
+
+        this._slider = new St.DrawingArea({ style_class: 'popup-slider-menu-item', reactive: true });
+        this.actor.set_child(this._slider);
+        this._slider.connect('repaint', Lang.bind(this, this._sliderRepaint));
+        this._slider.connect('button-press-event', Lang.bind(this, this._startDragging));
+
+        this._releaseId = this._motionId = 0;
+        this._dragging = false;
+    },
+
+    setValue: function(value) {
+        if (isNaN(value))
+            throw TypeError('The slider value must be a number');
+
+        this._displayValue = this._value = Math.max(Math.min(value, 1), 0);
+        this._slider.queue_repaint();
+    },
+
+    _sliderRepaint: function(area) {
+        let cr = area.get_context();
+        let themeNode = area.get_theme_node();
+        let [width, height] = area.get_surface_size();
+
+        let found, handleRadius;
+        [found, handleRadius] = themeNode.get_length('-slider-handle-radius', false);
+
+        let sliderWidth = width - 2 * handleRadius;
+        let sliderHeight;
+        [found, sliderHeight] = themeNode.get_length('-slider-height', false);
+
+        let sliderBorderWidth;
+        [found, sliderBorderWidth] = themeNode.get_length('-slider-border-width', false);
+
+        let sliderBorderColor = new Clutter.Color();
+        themeNode.get_color('-slider-border-color', false, sliderBorderColor);
+        let sliderColor = new Clutter.Color();
+        themeNode.get_color('-slider-background-color', false, sliderColor);
+
+        cr.setSourceRGBA (
+            sliderColor.red / 255,
+            sliderColor.green / 255,
+            sliderColor.blue / 255,
+            sliderColor.alpha / 255);
+        cr.rectangle(handleRadius, (height - sliderHeight) / 2, sliderWidth, sliderHeight);
+        cr.fillPreserve();
+        cr.setSourceRGBA (
+            sliderBorderColor.red / 255,
+            sliderBorderColor.green / 255,
+            sliderBorderColor.blue / 255,
+            sliderBorderColor.alpha / 255);
+        cr.setLineWidth(sliderBorderWidth);
+        cr.stroke();
+
+        let handleY = height / 2;
+        let handleX = handleRadius + (width - 2 * handleRadius) * this._displayValue;
+
+        let color = new Clutter.Color();
+        themeNode.get_foreground_color(color);
+        cr.setSourceRGBA (
+            color.red / 255,
+            color.green / 255,
+            color.blue / 255,
+            color.alpha / 255);
+        cr.arc(handleX, handleY, handleRadius, 0, 2 * Math.PI);
+        cr.fill();
+    },
+
+    _startDragging: function(actor, event) {
+        if (this._dragging) // don't allow two drags at the same time
+            return;
+
+        this._dragging = true;
+
+        // FIXME: we should only grab the specific device that originated
+        // the event, but for some weird reason events are still delivered
+        // outside the slider if using clutter_grab_pointer_for_device
+        Clutter.grab_pointer(this._slider);
+        this._releaseId = this._slider.connect('button-release-event', Lang.bind(this, this._endDragging));
+        this._motionId = this._slider.connect('motion-event', Lang.bind(this, this._motionEvent));
+        let absX, absY;
+        [absX, absY] = event.get_coords();
+        this._moveHandle(absX, absY);
+    },
+
+    _endDragging: function() {
+        if (this._dragging) {
+            this._slider.disconnect(this._releaseId);
+            this._slider.disconnect(this._motionId);
+
+            Clutter.ungrab_pointer();
+            this._dragging = false;
+
+            this._value = this._displayValue;
+            this.emit('value-changed', this._value);
+        }
+        return true;
+    },
+
+    _motionEvent: function(actor, event) {
+        let absX, absY;
+        [absX, absY] = event.get_coords();
+        this._moveHandle(absX, absY)
+        return true;
+    },
+
+    _moveHandle: function(absX, absY) {
+        let relX, relY, sliderX, sliderY;
+        [sliderX, sliderY] = this._slider.get_transformed_position();
+        relX = absX - sliderX;
+        relY = absY - sliderY;
+
+        let width = this._slider.width;
+        let found, handleRadius;
+        [found, handleRadius] = this._slider.get_theme_node().get_length('-slider-handle-radius', false);
+
+        let newvalue;
+        if (relX < handleRadius)
+            newvalue = 0;
+        else if (relX > width - handleRadius)
+            newvalue = 1;
+        else
+            newvalue = (relX - handleRadius) / (width - 2 * handleRadius);
+        this._displayValue = newvalue;
+        this._slider.queue_repaint();
+    },
+
+    get value() {
+        return this._value;
+    },
+
+    handleKeyPress: function(event) {
+        let key = event.get_key_symbol();
+        if (key == Clutter.Right || key == Clutter.Left) {
+            let delta = key == Clutter.Right ? 0.1 : -0.1;
+            this._value = this._displayValue = Math.max(0, Math.min(this._value + delta, 1));
+            this._slider.queue_repaint();
+            this.emit('value-changed', this._value);
+            return true;
+        }
+        return false;
+    }
+}
+
 function PopupSwitchMenuItem() {
     this._init.apply(this, arguments);
 }